package controllers.validation;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import models.CNAverage;
import models.NodeConfig;
import models.SensorConfig;
import models.SensorData;
import models.UserConfig;
import org.apache.log4j.Logger;
import play.Play;
import play.jobs.Every;
import play.jobs.Job;
import ch.bfh.abch.rems.REMSMulticastController;
import ch.bfh.abch.rems.sender.tinkerforge.TFMulticastSender;
import ch.bfh.abcm.rems.gateway.GateWayMessageType;
import ch.bfh.abcm.rems.gateway.GatewayMessage;
import ch.bfh.abcm.rems.sensors.tinkerforge.TFSensorType;
import ch.bfh.abcm.rems.tinkerforge.TFNodeAverageMessage;

public class ValidationJob extends Job {
    
    private static String NODE = Play.configuration.getProperty("own.node.ip", "192.168.1.47");
    private Logger validationJobLogger = Logger.getLogger("validationJob");
    private TFMulticastSender sender;
    private String measurementpoint;
    private List<SensorConfig> sensors;
    private List<String> nodes;
    private TFSensorType sensorType;

    public ValidationJob(String measurementpoint) {
        this.measurementpoint = measurementpoint;
        sender = new REMSMulticastController().getMulticastSender();
    }

    public void doJob() throws Exception {
        // JOB_START
    	String js_msg = "JOB_START [MP: " + measurementpoint + "]";
        validationJobLogger.debug("---" + js_msg + "---");
        ArrayList<Integer> averageList = new ArrayList<Integer>();
        sensors = SensorConfig.getSensors(measurementpoint);
        // SENSOR_RESPONDING / SENSOR_NOT_RESPONDING
        for (SensorConfig sensor : sensors) {
            SensorData measurements = SensorData.getLastMeasurement(sensor.getUid());
            if (!isComponentResponding(sensor.getUid(), measurements.getTimestamp())) {
            	String snr_msg = "SENSOR_NOT_RESPONDING [MP: " + sensor.getMeasurementPointIP() + "] [T: " + sensor.getSensorType() + "] [UID: " + sensor.getUid() + "]";
                validationJobLogger.error(snr_msg);
                play.Logger.error(snr_msg);
                sendGatewayMessage(snr_msg, GateWayMessageType.SYSTEM_ERROR);
                return;
            }
            String sr_msg = "SENSOR_RESPONDING [MP: " + sensor.getMeasurementPointIP() + "] [T: " + sensor.getSensorType() + "] [UID: " + sensor.getUid() + "]";
            validationJobLogger.info(sr_msg);
            play.Logger.info(sr_msg);
            averageList.add(medianLastFiveMeasurements(sensor.getUid()));
        }
        // SENSOR_VALUES_IN_RANGE / SENSOR_VALUES_OUT_OF_RANGE
        // TODO: Not checked (empty, division by zero, ...)
        String maxdiffKey = sensors.get(0).getSensorType() + UserConfig.MAX_DIVERGENT_KEY_EXT;
        play.Logger.debug("maxdiffKey: %s", maxdiffKey);
        if (!checkMaxDivergent(averageList, maxdiffKey)) {
            return;
        }
        calcAndSendNodeMedian(averageList, sensorType);
        nodes = NodeConfig.getNodes();
        averageList.clear();
        // NODE_RESPONDING / NODE_NOT_RESPONDING
        for (String node : nodes) {
            play.Logger.debug(" TAAAAG Node: %s measurementpoint: %s", node, measurementpoint);
            CNAverage lastAverage = CNAverage.getLastCNAvaerage(node, measurementpoint);
            if (lastAverage == null) {
                // TODO: some shit
                return;
            }
            if (!isComponentResponding(node, lastAverage.getTimestamp())) {
            	String nnr_msg = "NODE_NOT_RESPONDING [N: " + node + "]";
                validationJobLogger.error(nnr_msg);
                play.Logger.error(nnr_msg);
                sendGatewayMessage(nnr_msg, GateWayMessageType.SYSTEM_ERROR);
                return;
            }
        	String nr_msg = "NODE_RESPONDING [N: " + node + "]";
        	play.Logger.info(nr_msg);
            validationJobLogger.info(nr_msg);
            averageList.add(lastAverage.getNodeValue());
        }
        // Check if the node average is OUT OF RANGE
        // Logger.info("Check if the node average is OUT OF RANGE");
        // if (!checkMaxDivergent(averageList, maxdiffKey)) {
        //     return;
        // }
        int average = calcEffektiveValue(averageList);
        // MP_MEASUREMENT_IN_RANGE / MP_MEASUREMENT_OUT_OF_RANGE
        if (!checkMaxDivergentUser(average, sensorType)) {
            return;
        }
        // MP_MEASUREMENT_OK
        String mmo_msg = "MP_MEASUREMENT_OK [MP: " + measurementpoint + "] [T: " + sensorType + "] [VAL: " + average + "]";
        validationJobLogger.info(mmo_msg);
        play.Logger.info(mmo_msg);
        sendGatewayMessage(mmo_msg, GateWayMessageType.SYSTEM_OK);
    }

    private void sendGatewayMessage(String text, GateWayMessageType type) {
        GatewayMessage message = new GatewayMessage(type, text, sensorType);
        validationJobLogger.info("Send "+message.toString());
        play.Logger.debug(message.toString());
        sender.sendGateWayMessage(message);
    }

    private boolean checkMaxDivergentUser(int average, TFSensorType type) {
        UserConfig ucmin = UserConfig.getProperty(type + UserConfig.MIN_VALUE_USER_KEY_EXT);
        UserConfig ucmax = UserConfig.getProperty(type + UserConfig.MAX_VALUE_USER_KEY_EXT);
        int min = Integer.valueOf(ucmin.getPropertyValue());
        int max = Integer.valueOf(ucmax.getPropertyValue());
        if (!(average > min && average < max)) {
        	String mmoor_msg = "MP_MEASUREMENT_OUT_OF_RANGE [MP: " + measurementpoint + "] [T: " + sensorType + "] [MIN: " + min + " MAX: " + max + " AVG: " + average + "]";
        	validationJobLogger.error(mmoor_msg);
            sendGatewayMessage(mmoor_msg, GateWayMessageType.MEASUREMENT_DIVERGENCE);
            return false;
        }
        String mmir_msg = "MP_MEASUREMENT_IN_RANGE [MP: " + measurementpoint + "] [T: " + sensorType + "] [MIN: " + min + " MAX: " + max + " AVG: " + average + "]";
    	validationJobLogger.info(mmir_msg);
    	play.Logger.debug(mmir_msg);
        return true;
    }

    // TODO: Not checked (empty, division by zero, ...)
    private int calcEffektiveValue(ArrayList<Integer> lastAverages) {
        int sum = 0;
        for (Integer integer : lastAverages) {
            sum = sum + integer;
        }
        return sum / lastAverages.size();
    }

    private void calcAndSendNodeMedian(ArrayList<Integer> averageLastFive, TFSensorType sensorType) throws UnknownHostException {
        Collections.sort(averageLastFive);
        Integer nodeAverage = averageLastFive.get(1);
		String ip = NODE;
        TFNodeAverageMessage tfNodeAverage = new TFNodeAverageMessage(nodeAverage, ip, measurementpoint, sensorType);
        validationJobLogger.debug("Send "+tfNodeAverage.toString());
        play.Logger.debug("Send %s", tfNodeAverage.toString());
        sender.sendTFNodeAverageMessage(tfNodeAverage);
    }

    private boolean checkMaxDivergent(ArrayList<Integer> averageLastFive, String key) {
        int max = Collections.max(averageLastFive);
        int min = Collections.min(averageLastFive);
        int diff = max - min;
        // TODO: Not checked (empty, division by zero, ...)
        sensorType = sensors.get(0).getSensorType();
        String maxdiff = UserConfig.getProperty(key).getPropertyValue();
        if (diff > Integer.valueOf(maxdiff)) {
        	String svoor_msg = "SENSOR_VALUES_OUT_OF_RANGE [MP: " + measurementpoint + "] [T: " + sensorType + "] [DIFF: " + diff + " > MAXDIFF: " + maxdiff + "]";
        	validationJobLogger.error(svoor_msg);
            sendGatewayMessage(svoor_msg, GateWayMessageType.SYSTEM_ERROR);
            return false;
        }
    	String svir_msg = "SENSOR_VALUES_IN_RANGE [MP: " + measurementpoint + "] [T: " + sensorType + "] [DIFF: " + diff + " < MAXDIFF: " + maxdiff + "]";
    	validationJobLogger.info(svir_msg);
        return true;
    }

    private Integer medianLastFiveMeasurements(String uid) {
        List<Integer> lastFiveSensorValues = SensorData.getLastFiveMeasurements(uid);
        Collections.sort(lastFiveSensorValues);
        return lastFiveSensorValues.get(2);
    }

    public boolean isComponentResponding(String id, long timestamp) {
        long now = new Date().getTime();
        long diff = now - timestamp;
        long maxdiff = Long.valueOf(UserConfig.getProperty(UserConfig.SENSOR_MAX_TIME_DIFF_MS).getPropertyValue());
        validationJobLogger.debug("isComponentResponding Diff:"+diff+" MaxDiff: "+maxdiff);
        return (diff < maxdiff);
    }
}